Added GtkTreeMenuHeaderFunc to decide if a submenu gets a leaf header.
authorTristan Van Berkom <tristan.van.berkom@gmail.com>
Sat, 20 Nov 2010 07:32:24 +0000 (16:32 +0900)
committerTristan Van Berkom <tristan.van.berkom@gmail.com>
Tue, 4 Jan 2011 14:37:06 +0000 (23:37 +0900)
GtkComboBox needs treemenus to allow selection of all leafs including
rows which may have children, this allows the combobox or combobox user
to decide which row that has children can also be selectable as a header
leaf of the submenu. Test case testtreemenu updated to reflect this.

gtk/gtktreemenu.c
gtk/gtktreemenu.h
tests/testtreemenu.c

index e80ed45179652aa6ea8f99702a39077370684e44..d6586b25bc4e386f51136d59554826aec853d01d 100644 (file)
@@ -24,6 +24,7 @@
 #include "config.h"
 #include "gtkintl.h"
 #include "gtktreemenu.h"
+#include "gtkmarshalers.h"
 #include "gtkmenuitem.h"
 #include "gtkseparatormenuitem.h"
 #include "gtkcellareabox.h"
@@ -91,10 +92,14 @@ static GtkWidget *gtk_tree_menu_create_item                   (GtkTreeMenu
                                                               GtkTreeIter          *iter);
 static void      gtk_tree_menu_set_area                       (GtkTreeMenu          *menu,
                                                               GtkCellArea          *area);
-static void      queue_resize_all                             (GtkWidget            *menu);
 static void      context_size_changed_cb                      (GtkCellAreaContext   *context,
                                                               GParamSpec           *pspec,
                                                               GtkWidget            *menu);
+static void      item_activated_cb                            (GtkMenuItem          *item,
+                                                              GtkTreeMenu          *menu);
+static void      submenu_activated_cb                         (GtkTreeMenu          *submenu,
+                                                              const gchar          *path,
+                                                              GtkTreeMenu          *menu);
 
 struct _GtkTreeMenuPrivate
 {
@@ -116,6 +121,11 @@ struct _GtkTreeMenuPrivate
   GtkTreeViewRowSeparatorFunc row_separator_func;
   gpointer                    row_separator_data;
   GDestroyNotify              row_separator_destroy;
+
+  /* Submenu headers */
+  GtkTreeMenuHeaderFunc header_func;
+  gpointer              header_data;
+  GDestroyNotify        header_destroy;
 };
 
 enum {
@@ -125,6 +135,13 @@ enum {
   PROP_CELL_AREA
 };
 
+enum {
+  SIGNAL_MENU_ACTIVATE,
+  N_SIGNALS
+};
+
+static guint tree_menu_signals[N_SIGNALS] = { 0 };
+
 G_DEFINE_TYPE_WITH_CODE (GtkTreeMenu, gtk_tree_menu, GTK_TYPE_MENU,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
                                                gtk_tree_menu_cell_layout_init));
@@ -158,6 +175,15 @@ gtk_tree_menu_class_init (GtkTreeMenuClass *class)
   widget_class->get_preferred_height           = gtk_tree_menu_get_preferred_height;
   widget_class->size_allocate                  = gtk_tree_menu_size_allocate;
 
+  tree_menu_signals[SIGNAL_MENU_ACTIVATE] =
+    g_signal_new (I_("menu-activate"),
+                 G_OBJECT_CLASS_TYPE (object_class),
+                 G_SIGNAL_RUN_FIRST,
+                 0, /* No class closure here */
+                 NULL, NULL,
+                 _gtk_marshal_VOID__STRING,
+                 G_TYPE_NONE, 1, G_TYPE_STRING);
+
   g_object_class_install_property (object_class,
                                    PROP_MODEL,
                                    g_param_spec_object ("model",
@@ -249,6 +275,17 @@ gtk_tree_menu_dispose (GObject *object)
 static void
 gtk_tree_menu_finalize (GObject *object)
 {
+  GtkTreeMenu        *menu;
+  GtkTreeMenuPrivate *priv;
+
+  menu = GTK_TREE_MENU (object);
+  priv = menu->priv;
+
+  gtk_tree_menu_set_row_separator_func (menu, NULL, NULL, NULL);
+  gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
+
+  if (priv->root) 
+    gtk_tree_row_reference_free (priv->root);
 
   G_OBJECT_CLASS (gtk_tree_menu_parent_class)->finalize (object);
 }
@@ -639,28 +676,9 @@ context_size_changed_cb (GtkCellAreaContext  *context,
       !strcmp (pspec->name, "natural-width") ||
       !strcmp (pspec->name, "minimum-height") ||
       !strcmp (pspec->name, "natural-height"))
-    queue_resize_all (menu);
+    gtk_widget_queue_resize (menu);
 }
 
-static void
-queue_resize_all (GtkWidget *menu)
-{
-  GList *children, *l;
-
-  children = gtk_container_get_children (GTK_CONTAINER (menu));
-  for (l = children; l; l = l->next)
-    {
-      GtkWidget *widget = l->data;
-
-      gtk_widget_queue_resize (widget);
-    }
-
-  g_list_free (children);
-
-  gtk_widget_queue_resize (menu);
-}
-
-
 static void
 gtk_tree_menu_set_area (GtkTreeMenu *menu,
                        GtkCellArea *area)
@@ -668,15 +686,14 @@ gtk_tree_menu_set_area (GtkTreeMenu *menu,
   GtkTreeMenuPrivate *priv = menu->priv;
 
   if (priv->area)
-    g_object_unref (area);
+    g_object_unref (priv->area);
 
   priv->area = area;
 
   if (priv->area)
-    g_object_ref_sink (area);
+    g_object_ref_sink (priv->area);
 }
 
-
 static GtkWidget *
 gtk_tree_menu_create_item (GtkTreeMenu *menu,
                           GtkTreeIter *iter)
@@ -700,6 +717,8 @@ gtk_tree_menu_create_item (GtkTreeMenu *menu,
   gtk_widget_show (view);
   gtk_container_add (GTK_CONTAINER (item), view);
 
+  g_signal_connect (item, "activate", G_CALLBACK (item_activated_cb), menu);
+
   return item;
 }
 
@@ -722,8 +741,23 @@ gtk_tree_menu_populate (GtkTreeMenu *menu)
   if (path)
     {
       if (gtk_tree_model_get_iter (priv->model, &parent, path))
-       valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
+       {
+         valid = gtk_tree_model_iter_children (priv->model, &iter, &parent);
 
+         if (priv->header_func && 
+             priv->header_func (priv->model, &parent, priv->header_data))
+           {
+             /* Add a submenu header for rows which desire one, used for
+              * combo boxes to allow all rows to be activatable/selectable 
+              */
+             menu_item = gtk_tree_menu_create_item (menu, &parent);
+             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+             
+             menu_item = gtk_separator_menu_item_new ();
+             gtk_widget_show (menu_item);
+             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+           }
+       }
       gtk_tree_path_free (path);
     }
   else
@@ -753,11 +787,26 @@ gtk_tree_menu_populate (GtkTreeMenu *menu)
              GtkWidget           *submenu;
              
              row_path = gtk_tree_model_get_path (priv->model, &iter);
-             submenu  = gtk_tree_menu_new_full (priv->area, priv->model, row_path);
+             submenu  = gtk_tree_menu_new_with_area (priv->area);
+
+             gtk_tree_menu_set_row_separator_func (GTK_TREE_MENU (submenu), 
+                                                   priv->row_separator_func,
+                                                   priv->row_separator_data,
+                                                   priv->row_separator_destroy);
+             gtk_tree_menu_set_header_func (GTK_TREE_MENU (submenu), 
+                                            priv->header_func,
+                                            priv->header_data,
+                                            priv->header_destroy);
+
+             gtk_tree_menu_set_model (GTK_TREE_MENU (submenu), priv->model);
+             gtk_tree_menu_set_root (GTK_TREE_MENU (submenu), row_path);
 
              gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), submenu);
 
              gtk_tree_path_free (row_path);
+
+             g_signal_connect (submenu, "menu-activate", 
+                               G_CALLBACK (submenu_activated_cb), menu);
            }
        }
 
@@ -767,6 +816,36 @@ gtk_tree_menu_populate (GtkTreeMenu *menu)
     }
 }
 
+static void
+item_activated_cb (GtkMenuItem          *item,
+                  GtkTreeMenu          *menu)
+{
+  GtkCellView *view;
+  GtkTreePath *path;
+  gchar       *path_str;
+
+  /* Only activate leafs, not parents */
+  if (!gtk_menu_item_get_submenu (item))
+    {
+      view     = GTK_CELL_VIEW (gtk_bin_get_child (GTK_BIN (item)));
+      path     = gtk_cell_view_get_displayed_row (view);
+      path_str = gtk_tree_path_to_string (path);
+      
+      g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path_str);
+      
+      g_free (path_str);
+      gtk_tree_path_free (path);
+    }
+}
+
+static void
+submenu_activated_cb (GtkTreeMenu          *submenu,
+                     const gchar          *path,
+                     GtkTreeMenu          *menu)
+{
+  g_signal_emit (menu, tree_menu_signals[SIGNAL_MENU_ACTIVATE], 0, path);
+}
+
 /****************************************************************
  *                            API                               *
  ****************************************************************/
@@ -865,8 +944,6 @@ gtk_tree_menu_set_root (GtkTreeMenu         *menu,
   /* Populate for the new root */
   if (priv->model)
     gtk_tree_menu_populate (menu);
-
-  gtk_widget_queue_resize (GTK_WIDGET (menu));
 }
 
 GtkTreePath *
@@ -902,6 +979,14 @@ gtk_tree_menu_set_row_separator_func (GtkTreeMenu          *menu,
   priv->row_separator_func    = func;
   priv->row_separator_data    = data;
   priv->row_separator_destroy = destroy;
+
+  /* Destroy all the menu items */
+  gtk_container_foreach (GTK_CONTAINER (menu), 
+                        (GtkCallback) gtk_widget_destroy, NULL);
+  
+  /* Populate again */
+  if (priv->model)
+    gtk_tree_menu_populate (menu);
 }
 
 GtkTreeViewRowSeparatorFunc
@@ -915,3 +1000,43 @@ gtk_tree_menu_get_row_separator_func (GtkTreeMenu *menu)
 
   return priv->row_separator_func;
 }
+
+void
+gtk_tree_menu_set_header_func (GtkTreeMenu          *menu,
+                              GtkTreeMenuHeaderFunc func,
+                              gpointer              data,
+                              GDestroyNotify        destroy)
+{
+  GtkTreeMenuPrivate *priv;
+
+  g_return_if_fail (GTK_IS_TREE_MENU (menu));
+
+  priv = menu->priv;
+
+  if (priv->header_destroy)
+    priv->header_destroy (priv->header_data);
+
+  priv->header_func    = func;
+  priv->header_data    = data;
+  priv->header_destroy = destroy;
+
+  /* Destroy all the menu items */
+  gtk_container_foreach (GTK_CONTAINER (menu), 
+                        (GtkCallback) gtk_widget_destroy, NULL);
+  
+  /* Populate again */
+  if (priv->model)
+    gtk_tree_menu_populate (menu);
+}
+
+GtkTreeMenuHeaderFunc
+gtk_tree_menu_get_header_func (GtkTreeMenu *menu)
+{
+  GtkTreeMenuPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_TREE_MENU (menu), NULL);
+
+  priv = menu->priv;
+
+  return priv->header_func;
+}
index 9a05678ad22203b624286765bc0a692a34e742c0..539613f161af678a7f434135c2ed47e7180046ef 100644 (file)
@@ -46,6 +46,9 @@ typedef struct _GtkTreeMenu              GtkTreeMenu;
 typedef struct _GtkTreeMenuClass         GtkTreeMenuClass;
 typedef struct _GtkTreeMenuPrivate       GtkTreeMenuPrivate;
 
+typedef gboolean (*GtkTreeMenuHeaderFunc) (GtkTreeModel      *model,
+                                          GtkTreeIter       *iter,
+                                          gpointer           data);
 
 struct _GtkTreeMenu
 {
@@ -89,6 +92,12 @@ void                        gtk_tree_menu_set_row_separator_func   (GtkTreeMenu
                                                                    GDestroyNotify        destroy);
 GtkTreeViewRowSeparatorFunc gtk_tree_menu_get_row_separator_func   (GtkTreeMenu          *menu);
 
+void                  gtk_tree_menu_set_header_func                (GtkTreeMenu          *menu,
+                                                                   GtkTreeMenuHeaderFunc func,
+                                                                   gpointer              data,
+                                                                   GDestroyNotify        destroy);
+GtkTreeMenuHeaderFunc gtk_tree_menu_get_header_func                (GtkTreeMenu          *menu);
+
 G_END_DECLS
 
 #endif /* __GTK_TREE_MENU_H__ */
index d099f0d3fc82d61fb9ace94f536c85c3de00e054..e514f4752221c7c68f7daa0b02370a08249e454b 100644 (file)
@@ -16,7 +16,7 @@ static GtkCellRenderer *cell_1 = NULL, *cell_2 = NULL, *cell_3 = NULL;
 static GtkTreeModel *
 simple_tree_model (void)
 {
-  GtkTreeIter   iter, parent;
+  GtkTreeIter   iter, parent, child;
   GtkTreeStore *store = 
     gtk_tree_store_new (N_SIMPLE_COLUMNS,
                        G_TYPE_STRING,  /* name text */
@@ -84,6 +84,27 @@ simple_tree_model (void)
                      SIMPLE_COLUMN_DESCRIPTION, "Eager",
                      -1);
 
+  gtk_tree_store_append (store, &child, &iter);
+  gtk_tree_store_set (store, &child, 
+                     SIMPLE_COLUMN_NAME, "Jump",
+                     SIMPLE_COLUMN_ICON, "gtk-yes",
+                     SIMPLE_COLUMN_DESCRIPTION, "Very High",
+                     -1);
+
+  gtk_tree_store_append (store, &child, &iter);
+  gtk_tree_store_set (store, &child, 
+                     SIMPLE_COLUMN_NAME, "Pounce",
+                     SIMPLE_COLUMN_ICON, "gtk-no",
+                     SIMPLE_COLUMN_DESCRIPTION, "On Pooh",
+                     -1);
+
+  gtk_tree_store_append (store, &child, &iter);
+  gtk_tree_store_set (store, &child, 
+                     SIMPLE_COLUMN_NAME, "Bounce",
+                     SIMPLE_COLUMN_ICON, "gtk-cancel",
+                     SIMPLE_COLUMN_DESCRIPTION, "Around",
+                     -1);
+
   gtk_tree_store_append (store, &iter, &parent);
   gtk_tree_store_set (store, &iter, 
                      SIMPLE_COLUMN_NAME, "Owl",
@@ -212,6 +233,44 @@ expand_cell_3_toggled (GtkToggleButton  *toggle,
   gtk_cell_area_cell_set (area, cell_3, "expand", expand, NULL);
 }
 
+static void
+menu_activated_cb (GtkTreeMenu *menu,
+                  const gchar *path,
+                  gpointer     unused)
+{
+  GtkTreeModel *model = gtk_tree_menu_get_model (menu);
+  GtkTreeIter   iter;
+  gchar        *row_name;
+
+  if (!gtk_tree_model_get_iter_from_string (model, &iter, path))
+    return;
+
+  gtk_tree_model_get (model, &iter, SIMPLE_COLUMN_NAME, &row_name, -1);
+
+  g_print ("Item activated: %s\n", row_name);
+
+  g_free (row_name);
+}
+
+gboolean 
+enable_submenu_headers (GtkTreeModel      *model,
+                       GtkTreeIter       *iter,
+                       gpointer           data)
+{
+  return TRUE;
+}
+
+
+static void
+submenu_headers_toggled (GtkToggleButton  *toggle,
+                        GtkTreeMenu      *menu)
+{
+  if (gtk_toggle_button_get_active (toggle))
+    gtk_tree_menu_set_header_func (menu, enable_submenu_headers, NULL, NULL);
+  else
+    gtk_tree_menu_set_header_func (menu, NULL, NULL, NULL);
+}
+
 static void
 tree_menu (void)
 {
@@ -224,6 +283,8 @@ tree_menu (void)
 
   menu = simple_tree_menu ();
 
+  g_signal_connect (menu, "menu-activate", G_CALLBACK (menu_activated_cb), NULL);
+
   vbox  = gtk_vbox_new (FALSE, 4);
   gtk_widget_show (vbox);
 
@@ -278,6 +339,15 @@ tree_menu (void)
   g_signal_connect (G_OBJECT (widget), "toggled",
                     G_CALLBACK (expand_cell_3_toggled), menu);
 
+  widget = gtk_check_button_new_with_label ("Submenu Headers");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), FALSE);
+  gtk_widget_show (widget);
+  gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+  
+  g_signal_connect (G_OBJECT (widget), "toggled",
+                    G_CALLBACK (submenu_headers_toggled), menu);
+
+
   gtk_container_add (GTK_CONTAINER (window), vbox);
 
   gtk_widget_show (window);